Domine a validação de Server Actions no React. Um mergulho profundo no processamento de formulários, melhores práticas de segurança e técnicas avançadas com Zod, useFormState e useFormStatus.
Validação de Server Actions no React: Um Guia Completo para Processamento de Entradas de Formulário e Segurança
A introdução das React Server Actions marcou uma mudança de paradigma significativa no desenvolvimento full-stack com frameworks como o Next.js. Ao permitir que componentes de cliente invoquem diretamente funções do lado do servidor, podemos agora construir aplicações mais coesas, eficientes e interativas com menos código repetitivo. No entanto, essa nova e poderosa abstração traz uma responsabilidade crítica para o primeiro plano: validação de entrada e segurança robustas.
Quando a fronteira entre cliente e servidor se torna tão fluida, é fácil ignorar os princípios fundamentais da segurança web. Qualquer entrada vinda de um usuário não é confiável e deve ser rigorosamente verificada no servidor. Este guia oferece uma exploração abrangente do processamento e validação de entradas de formulário dentro das React Server Actions, cobrindo tudo, desde princípios básicos até padrões avançados e prontos para produção que garantem que sua aplicação seja amigável ao usuário e segura.
O que Exatamente São as React Server Actions?
Antes de mergulhar na validação, vamos recapitular brevemente o que são as Server Actions. Em essência, são funções que você define no servidor, mas que podem ser executadas a partir do cliente. Quando um usuário envia um formulário ou clica num botão, uma Server Action pode ser chamada diretamente, contornando a necessidade de criar manualmente endpoints de API, lidar com requisições `fetch` e gerenciar estados de carregamento/erro.
Elas são construídas sobre a base dos formulários HTML e da API `FormData` da Plataforma Web, tornando-as progressivamente aprimoradas por padrão. Isso significa que seus formulários funcionarão mesmo que o JavaScript falhe ao carregar, proporcionando uma experiência de usuário resiliente.
Exemplo de uma Server Action básica:
// app/actions.js
'use server';
export async function createUser(formData) {
const name = formData.get('name');
const email = formData.get('email');
// ... lógica para salvar o usuário no banco de dados
console.log('Criando usuário:', { name, email });
}
// app/page.js
import { createUser } from './actions';
export default function UserForm() {
return (
);
}
Essa simplicidade é poderosa, mas também esconde a complexidade do que está acontecendo. A função `createUser` está sendo executada exclusivamente no servidor, mas é invocada a partir de um componente de cliente. Essa linha direta com a sua lógica de servidor é precisamente por isso que a validação não é apenas um recurso — é um requisito.
A Importância Inabalável da Validação
No mundo das Server Actions, cada função é um portão aberto para o seu servidor. A validação adequada atua como o guarda nesse portão. Eis por que é inegociável:
- Integridade dos Dados: Seu banco de dados e o estado da aplicação dependem de dados limpos e previsíveis. A validação garante que você não armazene endereços de e-mail malformados, strings vazias onde deveriam estar nomes, ou texto num campo destinado a números.
- Experiência do Usuário (UX) Aprimorada: Usuários cometem erros. Mensagens de erro claras, imediatas e específicas ao contexto os guiam para corrigir suas entradas, reduzindo a frustração e melhorando as taxas de conclusão de formulários.
- Segurança Sólida: Este é o aspecto mais crítico. Sem a validação do lado do servidor, sua aplicação fica vulnerável a uma série de ataques, incluindo:
- Injeção de SQL: Um ator mal-intencionado poderia enviar comandos SQL num campo de formulário para manipular seu banco de dados.
- Cross-Site Scripting (XSS): Se você armazenar e renderizar entradas de usuário não sanitizadas, um invasor poderia injetar scripts maliciosos que são executados nos navegadores de outros usuários.
- Negação de Serviço (DoS): Enviar dados inesperadamente grandes ou computacionalmente caros poderia sobrecarregar os recursos do seu servidor.
Validação do Lado do Cliente vs. Lado do Servidor: Uma Parceria Necessária
É importante entender que a validação deve acontecer em dois lugares:
- Validação do Lado do Cliente: Esta é para a UX. Ela fornece feedback instantâneo sem uma viagem de ida e volta à rede. Você pode usar atributos simples de HTML5 como `required`, `minLength`, `pattern`, ou JavaScript para verificar formatos enquanto o usuário digita. No entanto, ela pode ser facilmente contornada desativando o JavaScript ou usando ferramentas de desenvolvedor.
- Validação do Lado do Servidor: Esta é para a segurança e integridade dos dados. É a fonte de verdade final da sua aplicação. Não importa o que aconteça no cliente, o servidor deve revalidar tudo o que recebe. As Server Actions são o lugar perfeito para implementar essa lógica.
Regra de ouro: use a validação do lado do cliente para uma melhor experiência do usuário, mas sempre confie apenas na validação do lado do servidor para a segurança.
Implementando Validação em Server Actions: Do Básico ao Avançado
Vamos construir nossa estratégia de validação, começando com uma abordagem simples e avançando para uma solução mais robusta e escalável usando ferramentas modernas.
Abordagem 1: Validação Manual e Retorno de Estado
A maneira mais simples de lidar com a validação é adicionar declarações `if` dentro da sua Server Action e retornar um objeto indicando sucesso ou falha.
// app/actions.js
'use server';
import { redirect } from 'next/navigation';
export async function createInvoice(formData) {
const customerName = formData.get('customerName');
const amount = formData.get('amount');
if (!customerName || customerName.trim() === '') {
return { success: false, message: 'O nome do cliente é obrigatório.' };
}
if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
return { success: false, message: 'Por favor, insira um valor válido maior que zero.' };
}
// ... lógica para criar a fatura no banco de dados
console.log('Fatura criada para', customerName, 'com o valor', amount);
redirect('/dashboard/invoices');
}
Essa abordagem funciona, mas tem uma grande falha de UX: requer um recarregamento completo da página para exibir a mensagem de erro. Não conseguimos mostrar facilmente a mensagem na própria página do formulário. É aqui que entram os hooks do React para Server Actions.
Abordagem 2: Usando `useFormState` para Tratamento de Erros Contínuo
O hook `useFormState` foi projetado especificamente para este propósito. Ele permite que uma Server Action retorne um estado que pode ser usado para atualizar a UI sem um evento de navegação completo. É a pedra angular do tratamento moderno de formulários com Server Actions.
Vamos refatorar nosso formulário de criação de faturas.
Passo 1: Atualize a Server Action
A action agora precisa aceitar dois argumentos: `prevState` e `formData`. Ela deve retornar um novo objeto de estado que o `useFormState` usará para atualizar o componente.
// app/actions.js
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
// Define a forma do estado inicial
const initialState = {
message: null,
errors: {},
};
export async function createInvoice(prevState, formData) {
const customerName = formData.get('customerName');
const amount = formData.get('amount');
const status = formData.get('status');
const errors = {};
if (!customerName || customerName.trim().length < 2) {
errors.customerName = 'O nome do cliente deve ter pelo menos 2 caracteres.';
}
if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
errors.amount = 'Por favor, insira um valor válido.';
}
if (status !== 'pending' && status !== 'paid') {
errors.status = 'Por favor, selecione um status válido.';
}
if (Object.keys(errors).length > 0) {
return {
message: 'Falha ao criar a fatura. Por favor, verifique os campos.',
errors,
};
}
try {
// ... lógica para salvar no banco de dados
console.log('Fatura criada com sucesso!');
} catch (e) {
return {
message: 'Erro no Banco de Dados: Falha ao criar a fatura.',
errors: {},
};
}
// Revalida o cache para a página de faturas e redireciona
revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}
Passo 2: Atualize o Componente do Formulário com `useFormState`
No nosso componente de cliente, usaremos o hook para gerenciar o estado do formulário и exibir os erros.
// app/ui/invoices/create-form.js
'use client';
import { useFormState } from 'react-dom';
import { createInvoice } from '@/app/actions';
const initialState = {
message: null,
errors: {},
};
export function CreateInvoiceForm() {
const [state, dispatch] = useFormState(createInvoice, initialState);
return (
);
}
Agora, quando o usuário envia um formulário inválido, a Server Action é executada, retorna o objeto de erro e o `useFormState` atualiza a variável `state`. O componente é renderizado novamente, exibindo as mensagens de erro específicas logo ao lado dos campos correspondentes — tudo sem um recarregamento de página. Isso é uma enorme melhoria na UX!
Abordagem 3: Aprimorando a UX com `useFormStatus`
O que acontece enquanto a Server Action está em execução? O usuário pode clicar no botão de envio várias vezes. Podemos fornecer feedback usando o hook `useFormStatus`, que nos dá informações sobre o status do último envio de formulário.
Importante: `useFormStatus` deve ser usado num componente que seja filho do elemento `